package com.example.photo.utils;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileTypeUtil;
import com.example.photo.common.ErrorCode;
import com.example.photo.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.bytedeco.javacv.Frame;
import org.bytedeco.javacv.Java2DFrameConverter;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.imageio.ImageIO;
import javax.servlet.ServletContext;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.UUID;

/**
 * 文件处理工具类
 *
 * @author Lwh
 * @time 2023/04/10 13:38
 */
@Slf4j
public class FileUtils {

    /**
     * 传入文件，获取存储路径
     * MultipartFile到Path
     *
     * @param imageFile   原始文件
     * @param isThumbnail 是否存储为缩略图
     * @return 存储路径
     */
    public static String getPathName(MultipartFile imageFile, boolean isThumbnail) {
        String uuid = UUID.randomUUID().toString();
        String fileDirectory = "media" + File.separator + DateUtil.format(new Date(), DateTimeFormatter.ofPattern("yyyy_MM"));

        //缩略图路径
        String thumbnailName = fileDirectory + File.separator + uuid + "_thumbnail.jpg";

        //原始图路径
        String originalFilename = imageFile.getOriginalFilename();
        log.info("原始文件名：{}", originalFilename);
        String extension = FilenameUtils.getExtension(originalFilename);
        log.info("文件拓展名：{}", extension);
        String originalName = fileDirectory + File.separator + uuid + "." + extension;

        String loadName = isThumbnail ? thumbnailName : originalName;
        log.info((isThumbnail ? "[缩略]" : "[原始]") + "图片上传路径：" + loadName);
        return loadName;
    }

    /**
     * 根据文件名获取文件类型
     *
     * @param filename 文件名
     * @return 文件类型
     */
    public static String getTypeByFilename(String filename, ServletContext servletContext) {
        String mimeType = servletContext.getMimeType(filename);
        if (mimeType == null) {
            mimeType = "application/octet-stream";
        }
        return mimeType;
    }

    /**
     * 根据路径获取File
     *
     * @param path 文件路径
     * @return File类型
     */
    public static File getFileByPath(String path) {
        return new File(Paths.get(path).toUri());
    }

    /**
     * 读取本地文件
     *
     * @param path 文件路径
     * @return MultipartFile类型
     */
    public static MultipartFile getMultiByPath(String path) {
        File file = getFileByPath(path);
        FileItem item = new DiskFileItemFactory().createItem("file"
                , MediaType.MULTIPART_FORM_DATA_VALUE
                , true
                , file.getName());
        try (InputStream input = Files.newInputStream(file.toPath());
             OutputStream os = item.getOutputStream()) {
            // 流转移
            IOUtils.copy(input, os);
        } catch (Exception e) {
            throw new IllegalArgumentException("Invalid file: " + e, e);
        }

        return new CommonsMultipartFile(item);
    }

    /**
     * 将路径转为Resource
     *
     * @param path 文件路径
     * @return Resource类型
     */
    public static Resource getResourceByPath(String path) {
        try {
            return new UrlResource(Paths.get(path).toUri());
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "文件未找到");
        }
    }

    /**
     * 将Resource类型的文件包装为ResponseEntity，根据文件名修改响应头信息
     *
     * @param resource 文件
     * @return 响应体
     */
    public static ResponseEntity<Resource> packToResponse(Resource resource, ServletContext servletContext) {
        if (resource.exists()) {
            // 获取文件类型，设置响应头
            String contentType = getTypeByFilename(resource.getFilename(), servletContext);
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.parseMediaType(contentType));
            headers.set(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"" + resource.getFilename() + "\"");

            return ResponseEntity.ok()
                    .headers(headers)
                    .contentType(MediaType.parseMediaType(contentType))
                    .body(resource);
        } else {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "文件不存在");
        }
    }

    /**
     * 保存原始图片
     *
     * @param multipartFile MultipartFile类型
     * @return 路径Path
     */
    public synchronized static String saveOriginal(MultipartFile multipartFile) {
        //拼接后台文件名称
        String originalPathName = getPathName(multipartFile, false);
        //判断文件保存是否存在
        File file = getFileByPath(originalPathName);
        if (file.getParentFile() != null || !file.getParentFile().isDirectory()) {
            //创建文件
            //noinspection ResultOfMethodCallIgnored
            file.getParentFile().mkdirs();
        }
        //写文件
        try {
            multipartFile.transferTo(file);
        } catch (IOException e) {
            e.printStackTrace();
            throw new BusinessException(ErrorCode.OPERATION_ERROR);
        }
        return originalPathName;
    }

    /**
     * 为图片创建缩略图
     *
     * @param originalPathName 原始文件路径
     * @return 路径Path
     */
    public static String createThumbnailForPic(String originalPathName) {
        MultipartFile imageFile = getMultiByPath(originalPathName);

        //拼接后台文件名称
        String thumbnailPathName = getPathName(imageFile, true);

        try {
            Thumbnails.of(originalPathName)
                    .width(800)
                    .outputQuality(0.8)
                    .outputFormat("jpg")
                    .toFile(getFileByPath(thumbnailPathName));
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException(ErrorCode.OPERATION_ERROR);
        }
        return thumbnailPathName;
    }

    /**
     * 获取指定视频的帧并保存为图片
     *
     * @param file      源视频文件地址
     * @param frameFile 截取帧的图片存放地址
     */
    public static void fetchPic(File file, String frameFile) {
        try (FFmpegFrameGrabber ff = new FFmpegFrameGrabber(file)) {
            ff.start();
            int length = ff.getLengthInFrames();
            String rotate_old = ff.getVideoMetadata("rotate"); //视频旋转角度，可能是null

            File targetFile = new File(frameFile);
            int i = 0;
            Frame frame = null;
            while (i < length) {
                // 过滤前5帧，避免出现全黑的图片
                frame = ff.grabFrame();
                if ((i > 50) && (frame.image != null)) {
                    break;
                }
                i++;
            }
            //默认指定后缀名为jpg
            String imgSuffix = "jpg";
            if (frameFile.indexOf('.') != -1) {
                String[] arr = frameFile.split("\\.");
                if (arr.length >= 2) {
                    imgSuffix = arr[1];
                }
            }
            //将帧转换为内存中的图片
            Java2DFrameConverter converter = new Java2DFrameConverter();
            BufferedImage srcBi = converter.getBufferedImage(frame);
            int oWidth = srcBi.getWidth();
            int oHeight = srcBi.getHeight();
            // 对截取的帧进行等比例缩放
            int width = 800;
            int height = (int) (((double) width / oWidth) * oHeight);
            BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
            bi.getGraphics().drawImage(srcBi.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null);

            ImageIO.write(bi, imgSuffix, targetFile);

            ff.stop();
            //若需要则旋转
            if (rotate_old != null && !rotate_old.isEmpty()) {
                int rotate = Integer.parseInt(rotate_old);
                rotatePhonePhoto(frameFile, imgSuffix, rotate);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 旋转照片
     *
     * @param fullPath 文件全路径
     * @param suffix 文件后缀名
     * @param angel 旋转角度
     */
    public static void rotatePhonePhoto(String fullPath, String suffix, int angel) {
        BufferedImage src;
        try {
            src = ImageIO.read(new File(fullPath));
            int src_width = src.getWidth(null);
            int src_height = src.getHeight(null);

            int swidth = src_width;
            int sheight = src_height;

            if (angel == 90 || angel == 270) {
                swidth = src_height;
                sheight = src_width;
            }
            Rectangle rect_des = new Rectangle(new Dimension(swidth, sheight));
            BufferedImage res = new BufferedImage(rect_des.width, rect_des.height, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2 = res.createGraphics();
            g2.translate((rect_des.width - src_width) / 2, (rect_des.height - src_height) / 2);
            g2.rotate(Math.toRadians(angel), (double) src_width / 2, (double) src_height / 2);
            g2.drawImage(src, null, null);
            ImageIO.write(res, suffix, new File(fullPath));
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 为某个文件创建封面
     * 需要区分图片和视频
     *
     * @param originalPath 原始文件路径
     * @return 封面文件路径
     */
    public static String getCover(String originalPath) {
        int fClass = getClass(originalPath);
        if (fClass == PIC) {
            //是图片
            return createThumbnailForPic(originalPath);
        } else if (fClass == VIDEO) {
            //是视频
            File file = getFileByPath(originalPath);
            try {
                MultipartFile multi = getMultiByPath(originalPath);
                String pathName = getPathName(multi, true);
                File picFile = new File(Paths.get(pathName).toUri());
                fetchPic(file, picFile.getAbsolutePath());
                return pathName;
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return "";
    }

    //图片
    public static final int PIC = 1;
    //视频
    public static final int VIDEO = 2;
    //不支持生成缩略图
    public static final int UNSUPPORTED = 3;

    /**
     * 判断是否是图片文件
     *
     * @param path 文件路径
     * @return 是图片吗？
     */
    public static int getClass(String path) {
        URI uri = Paths.get(path).toUri();
        String t = FileTypeUtil.getTypeByPath(String.valueOf(uri));
        switch (t) {
            case "jpg":
            case "png":
                return PIC;
            case "HEIC":
            case "gif":
                return UNSUPPORTED;
            case "mp4":
            default:
                return VIDEO;
        }
    }

    /**
     * 删除文件
     *
     * @param path 文件路径
     */
    public static void removeByPath(String path) {
        File file = getFileByPath(path);
        boolean delete = file.delete();
        if (!delete) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "删除文件失败");
        }
    }

}
